package com.moogene.android.engine.httpclient;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.util.EntityUtils;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.http.AndroidHttpClient;
import android.os.Build;

/**
 * This class encapsulates resource management and allows focus on your
 * application task instead of writing boilerplate code.
 *
 * @file HttpClientTemplate.java
 *
 */

public class HttpClientTemplate {
    private final static String USER_AGENT = "Android/" + Build.VERSION.RELEASE;

    /**
     * The HttpClient to use
     */
    private final HttpClient client;

    /**
     * Default validator is used when no specific validator is passed
     */
    private final HttpStatusValidator defaultValidator = new DefaultHttpStatusValidator();

    /**
     * Parser used to return Strings
     */
    private final EntityParser<String> toStringParser = new StringParser();

    /**
     * Parser used to return byte array
     */
    private final EntityParser<byte[]> toBytesParser = new ByteArrayParser();

    /**
     * Parser used to return Bitmap
     */
    private final EntityParser<Bitmap> toBitmapParser = new BitmapParser();

    private static HttpClientTemplate defaultHttpClientTemplate;

    public static HttpClientTemplate getInstance() {
        if (defaultHttpClientTemplate == null) {
            AndroidHttpClient androidHttpClient = AndroidHttpClient.newInstance(USER_AGENT);
            defaultHttpClientTemplate = new HttpClientTemplate(androidHttpClient);
        }
        return defaultHttpClientTemplate;
    }

    /**
     * Constructor where you should supply the {@link HttpClient} implementation
     * of your choice
     *
     * @param client the {@link HttpClient}
     */
    private HttpClientTemplate(HttpClient client) {
        this.client = client;
    }

    /**
     * Executes an http request and delegates the {@link HttpEntity} to the
     * specified {@link EntityParser}
     *
     * @param req the {@link HttpGet}, {@link HttpPost}, {@link HttpPut} or
     *            {@link HttpDelete} to use
     * @param parser an {@link EntityParser} to use
     * @return The object returned from the {@link EntityParser}
     * @throws HttpRequestException -if status code does not match the
     *             {@link HttpStatusValidator} (default is 200)
     *
     *             -if HttpEntity was null
     *
     *             -any {@link Exception} that might be thrown during execution
     *             of request or thrown by {@link EntityParser} is wapped
     */
    public <T> T request(HttpUriRequest req, EntityParser<T> parser) throws HttpRequestException {
        return request(req, parser, defaultValidator);
    }

    /**
     * Executes an http request and delegates the {@link HttpEntity} to the
     * specified {@link EntityParser}
     *
     * @param req the {@link HttpGet}, {@link HttpPost}, {@link HttpPut} or
     *            {@link HttpDelete} to use
     * @param parser an {@link EntityParser} to use
     * @param validator an {@link HttpStatusValidator} to use
     * @return The object returned from the {@link EntityParser}, which should
     *         never be null
     * @throws HttpRequestException -if status code does not match the
     *             {@link HttpStatusValidator} (default is 200)
     *
     *             -if HttpEntity was null
     *
     *             -any {@link Exception} that might be thrown during execution
     *             of request or thrown by {@link EntityParser} is wapped
     */
    public <T> T request(HttpUriRequest req, EntityParser<T> parser, HttpStatusValidator validator)
            throws HttpRequestException {
        HttpResponse response = null;

        try {
            response = client.execute(req);
            if (!validator.isStatusCodeValid(response.getStatusLine().getStatusCode())) {
                throw new HttpRequestException("Invalid statuscode:"
                        + response.getStatusLine().getStatusCode());
            }
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                return parser.parseEntity(entity);
            } else {
                throw new HttpRequestException("HttpEntity was null");
            }
        } catch (HttpRequestException e) {
            // Catch and re-throw to prevent from being caught and
            // wrapped when catching Exception
            throw e;
        } catch (Exception e) {
            throw new HttpRequestException(e);
        } finally {
            closeQuietly(response);
        }
    }

    /**
     * Simple method for getting the response as String
     *
     * @param req the GET,POST,PUT or DELETE operation
     * @return response as String
     * @throws HttpRequestException - if an error occurs
     */
    public String requestForString(HttpUriRequest req) throws HttpRequestException {
        return request(req, toStringParser);
    }

    /**
     * Simple method for getting the response as byte array
     *
     * @param req the GET,POST,PUT or DELETE operation
     * @return response as byte array
     * @throws HttpRequestException - if an error occurs
     */
    public byte[] requestForBytes(HttpUriRequest req) throws HttpRequestException {
        return request(req, toBytesParser);
    }

    /**
     * Simple method for getting the response as Bitmap
     *
     * @param req the GET,POST,PUT or DELETE operation
     * @return response as Bitmap
     * @throws HttpRequestException - if an error occurs
     */
    public Bitmap requestForBitmap(HttpUriRequest req) throws HttpRequestException {
        return request(req, toBitmapParser);
    }

    public void requestTracking(HttpUriRequest req) throws HttpRequestException {
        HttpResponse response = null;

        try {
            response = client.execute(req);
            if (!defaultValidator.isStatusCodeValid(response.getStatusLine().getStatusCode())) {
                throw new HttpRequestException("Invalid statuscode:"
                        + response.getStatusLine().getStatusCode());
            }

        } catch (HttpRequestException e) {
            // Catch and re-throw to prevent from being caught and
            // wrapped when catching Exception
            throw e;
        } catch (Exception e) {
            throw new HttpRequestException(e);
        } finally {
            closeQuietly(response);
        }
    }

    private void closeQuietly(HttpResponse resp) {
        if(resp == null){
            return;
        }
        try {
            resp.getEntity().getContent().close();
        } catch (Exception e) {
        }
    }

    private static class DefaultHttpStatusValidator implements HttpStatusValidator {

        public boolean isStatusCodeValid(int statusCode) {
            return statusCode == HttpStatus.SC_OK;
        }
    }

    private static class StringParser implements EntityParser<String> {
        public String parseEntity(HttpEntity entity) throws Exception {
            return EntityUtils.toString(entity);
        }
    }

    private static class ByteArrayParser implements EntityParser<byte[]> {
        public byte[] parseEntity(HttpEntity entity) throws Exception {
            return EntityUtils.toByteArray(entity);
        }
    }

    private static class BitmapParser implements EntityParser<Bitmap> {
        public Bitmap parseEntity(HttpEntity entity) throws Exception {
            byte b[] = EntityUtils.toByteArray(entity);
            return BitmapFactory.decodeByteArray(b, 0, b.length);
        }
    }
}
